package concurrecy.lock3;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;

class JannaReentrantReadWrite implements ReadWriteLock {

    private Sync sync = new Sync();
    private Lock readLock = new JannaSharedLock(sync);
    private Lock writeLock = new JannaLock(sync);

    private class Sync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            int s, r, w;
            s = getState();
            r = getReadState(s);
            w = getWriteState(s);
            if(s == 0) {
                if(compareAndSetState(s, generateState(w + arg, r))) {
                    setExclusiveOwnerThread(Thread.currentThread());
                    return true;
                }
            } else {
                if(w != 0 && getExclusiveOwnerThread() == Thread.currentThread()) {
                    setState(generateState(w + arg, r));
                    return true;
                }
            }
            return false;
        }

        private int getReadState(int state) {
            // 低16位为读锁
            return state & 0xffff;
        }

        private int getWriteState(int state) {
            // s 的高16位为写锁
            return state >>> 16;
        }

        private int generateState(int writeState, int readState) {
            return writeState << 16 | readState;
        }

        @Override
        protected boolean tryRelease(int arg) {
            if(Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            int s, r, w;
            s = getState();
            r = getReadState(s);
            w = getWriteState(s);
            boolean b = false;
            if(w - arg == 0) {
                b = true;
                setExclusiveOwnerThread(null);
            }
            setState(generateState(w - arg, r));
            return b;
        }

        @Override
        protected int tryAcquireShared(int arg) {
            int s, r, w;
            do {
                s = getState();
                w = getWriteState(s);
                // 如果写锁不为0，并且持有写锁的线程不是自己，则返回失败。 如果持有写锁的线程是自己，则锁降级
                if(w != 0 && getExclusiveOwnerThread() != Thread.currentThread())
                    return -1;
                r = getReadState(s);
            } while(!compareAndSetState(s, generateState(w, r + arg)));
            return 1;
        }

        @Override
        protected boolean tryReleaseShared(int arg) {
            int s, r, nextR;
            do {
                s = getState();
                r = getReadState(s);
                nextR = r - arg;
            } while (!compareAndSetState(s, generateState(getWriteState(s), nextR)));
            return nextR == 0;
        }
    }

    @Override
    public Lock readLock() {
        return readLock;
    }

    @Override
    public Lock writeLock() {
        return writeLock;
    }
}

public class MyReadWriteLockDemo {
    /**
     * 测试ReadWriteLock
     */
    static void test(ReadWriteLock lock) {
        for (int i = 0; i < 2; i++) {
            final int ii = i;
            new Thread(() -> {
                Thread.currentThread().setName("write" + ii);
                System.out.println("write" + ii + " running");
                lock.writeLock().lock();
                try {
                    System.out.println("write" + ii + " get write lock");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.writeLock().unlock();
                    System.out.println("write" + ii + " release write lock");
                }
            }).start();
        }

        for (int i = 0; i < 5; i++) {
            final int ii = i;
            new Thread(() -> {
                Thread.currentThread().setName("read" + ii);
                System.out.println("read" + ii + " running");
                lock.readLock().lock();
                try {
                    System.out.println("read" + ii + " get read lock");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.readLock().unlock();
                    System.out.println("read" + ii + " release read lock");
                }
            }).start();
        }
    }

    public static void main(String[] args) {
        ReadWriteLock lock = new JannaReentrantReadWrite();
        test(lock);
    }
}
